#coding=utf-8
# Copyright 2012-2013 Maxim Petrov <maximpetrov@yahoo.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 3 or any later version.
from panel import Panel
from controls import Pad
from difflib import Differ
import curses

class DiffPanel(Panel):
    """
    Panel for show difference between two files.
    """

    def __init__(self, main_window):
        """
        Constructor of diff panel.

        Keyword arguments:
            main_window -- instance of MainWindow
        """

        self._main_window = main_window
        self._old_file = []
        self._new_file = []
        self._titles = None
        self._left = Pad(main_window, lambda w, h: (1, 1, w / 2 - 3, h - 2),
                    lambda s, c: ((f[0].rstrip(), None,
                                [(0, max(self.__total_width, self._left.width, self._right.width), f[2])] + f[3:])
                                                            for f in self._old_file[s:s+c]))
        self._right = Pad(main_window, lambda w, h: (w / 2 + 2, 1, w - w / 2 - 3, h - 2),
                    lambda s, c: ((f[0].rstrip(), None,
                                [(0, max(self.__total_width, self._left.width, self._right.width), f[2])] + f[3:])
                                                            for f in self._new_file[s:s+c]))
        self._middle = Pad(main_window, lambda w, h: (w / 2 - 1, 1, 2, h - 2),
                    lambda s, c: ((u'', None, [(0, 1, f[0][1]), (1, 1, f[1][1])])
                                for f in zip(*[self._old_file[s:s+c], self._new_file[s:s+c]])))

    def redraw(self, window):
        """
        Function that redraw panel when window for draw changed

        Keyword arguments:
            window -- subwindow for draw panel
        """

        self._window = window
        if not self.visible:
            return
        y, x = window.getbegyx()
        h, w = window.getmaxyx()
        window.subwin(h, w / 2 - 1, y, x).box()
        window.subwin(h, w - w / 2 - 1, y, x + w / 2 + 1).box()
        if self._titles is not None:
            window.addstr(0, 3, self._main_window.str_to_external(self._titles[0][:w / 2 - 4]))
            window.addstr(0, w / 2 + 4, self._main_window.str_to_external(self._titles[1][:w - (w / 2 + 4)]))
        self._left.draw(x, y, w, h)
        self._right.draw(x, y, w, h)
        self._middle.draw(x, y, w, h)
        window.refresh()

    def set_content(self, file1, file2, titles=None):
        """
        Set content of pad (file for diff)
        """

        def parse_diff_line(line):
            """
            Parse line contains diffs for string returned by Differ.compare
            (starts with "? ")
            """

            modes = {u'+': self._main_window.color_scheme.inserted_line, u'-': self._main_window.color_scheme.removed_line, u'^': self._main_window.color_scheme.changed_line}
            result = []
            in_diff = False
            for i, ch in enumerate(line[2:]):
                if in_diff and ch != mode:
                    in_diff = False
                    result += [(start, i - start, modes[mode])]
                if not in_diff and ch in (u'+', u'-', u'^'):
                    in_diff = True
                    start = i
                    mode = ch
            if in_diff:
                result += [(start, i - start + 1, modes[mode])]
            return result

        self._titles = titles
        idx1 = 0
        idx2 = 0
        self._old_file = []
        self._new_file = []
        old_changed_lines = []
        new_changed_lines = []
        d = Differ()
        last = []
        while idx1 < len(file1) or idx2 < len(file2) or len(old_changed_lines) > 0 or len(new_changed_lines) > 0:
            if (idx1 < len(file1) and not file1[idx1][1] and idx2 < len(file2) and not file2[idx2][1]) or\
               (idx1 == len(file1) and idx2 == len(file2) and len(old_changed_lines) > 0 and len(new_changed_lines) > 0):
                compare_parse_state = 0
                for line in d.compare(old_changed_lines, new_changed_lines):
                    line = unicode(line)
                    if compare_parse_state == 0:
                        if line[0] == u'-':
                            last = [[line[2:], self._main_window.color_scheme.changed_line, curses.A_NORMAL]]
                            compare_parse_state = 1
                        else:
                            self._new_file += [[line[2:], self._main_window.color_scheme.inserted_line, curses.A_NORMAL]]
                            self._old_file += [[u'', curses.A_NORMAL, curses.A_NORMAL]]
                    elif compare_parse_state == 1:
                        if line[0] == u'+':
                            last += [[line[2:], self._main_window.color_scheme.changed_line, curses.A_NORMAL]]
                            compare_parse_state = 2
                        elif line[0] == u'?':
                            last[0] += parse_diff_line(line)
                            compare_parse_state = 3
                        else:
                            last[0][1] = self._main_window.color_scheme.removed_line
                            self._old_file.append(last[0])
                            self._new_file.append([u'', curses.A_NORMAL, curses.A_NORMAL])
                            last = [[line[2:], self._main_window.color_scheme.changed_line, curses.A_NORMAL]]
                            compare_parse_state = 1
                    elif compare_parse_state == 2:
                        self._old_file.append(last[0])
                        if line[0] == u'?':
                            last[1] += parse_diff_line(line)
                            compare_parse_state = 0
                        else:
                            last[0][1] = self._main_window.color_scheme.removed_line
                            last[1][1] = self._main_window.color_scheme.inserted_line
                        self._new_file.append(last[1])
                        if line[0] == u'-':
                            last = [[line[2:], self._main_window.color_scheme.changed_line, curses.A_NORMAL]]
                            compare_parse_state = 1
                        elif line[0] == u'+':
                            self._new_file += [[line[2:], self._main_window.color_scheme.inserted_line, curses.A_NORMAL]]
                            self._old_file += [[u'', curses.A_NORMAL, curses.A_NORMAL]]
                            compare_parse_state = 0
                    elif compare_parse_state == 3:
                        if line[0] == u'+':
                            last += [[line[2:], self._main_window.color_scheme.changed_line, curses.A_NORMAL]]
                            compare_parse_state = 4
                        # Really unexpected cause (after u'-' with u'?' should be u'+')
                        else:
                            self._old_file.append(last[0])
                            self._new_file.append([u'', curses.A_NORMAL, curses.A_NORMAL])
                            last = [[line[2:], self._main_window.color_scheme.removed_line, curses.A_NORMAL]]
                            compare_parse_state = 1
                    elif compare_parse_state == 4:
                        if line[0] == u'?':
                            last[1] += parse_diff_line(line)
                            compare_parse_state = 0
                        self._old_file.append(last[0])
                        self._new_file.append(last[1])
                        if line[0] == u'-':
                            last = [[line[2:], self._main_window.color_scheme.changed_line, curses.A_NORMAL]]
                            compare_parse_state = 1
                        elif line[0] == u'+':
                            self._new_file += [[line[2:], self._main_window.color_scheme.inserted_line, curses.A_NORMAL]]
                            self._old_file += [[u'', curses.A_NORMAL, curses.A_NORMAL]]
                            compare_parse_state = 0
                if compare_parse_state != 0:
                    self._old_file.append(last[0])
                    if compare_parse_state == 2:
                        last[0][1] = self._main_window.color_scheme.removed_line
                        last[1][1] = self._main_window.color_scheme.inserted_line
                        self._new_file.append(last[1])
                    elif compare_parse_state == 4:
                        self._new_file.append(last[1])
                    else:
                        last[0][1] = self._main_window.color_scheme.removed_line
                        self._new_file.append([u'', curses.A_NORMAL, curses.A_NORMAL])
                old_changed_lines = []
                new_changed_lines = []
            if idx1 < len(file1) and not file1[idx1][1] and idx2 < len(file2) and not file2[idx2][1]:
                self._old_file += [[file1[idx1][0], curses.A_NORMAL, curses.A_NORMAL]]
                self._new_file += [[file2[idx2][0], curses.A_NORMAL, curses.A_NORMAL]]
                idx1 += 1
                idx2 += 1
            elif idx1 < len(file1) and file1[idx1][1] and idx2 < len(file2) and file2[idx2][1]:
                old_changed_lines += [file1[idx1][0]]
                new_changed_lines += [file2[idx2][0]]
                idx1 += 1
                idx2 += 1
            elif idx1 < len(file1) and file1[idx1][1]:
                if len(new_changed_lines) > 0:
                    old_changed_lines += [file1[idx1][0]]
                else:
                    self._old_file += [[file1[idx1][0], self._main_window.color_scheme.removed_line, curses.A_NORMAL]]
                    self._new_file += [[u'', curses.A_NORMAL, curses.A_NORMAL]]
                idx1 += 1
            elif idx2 < len(file2) and file2[idx2][1]:
                if len(old_changed_lines) > 0:
                    new_changed_lines += [file2[idx2][0]]
                else:
                    self._new_file += [[file2[idx2][0], self._main_window.color_scheme.inserted_line, curses.A_NORMAL]]
                    self._old_file += [[u'', curses.A_NORMAL, curses.A_NORMAL]]
                idx2 += 1

        for l in self._old_file + self._new_file:
            if l[1] == self._main_window.color_scheme.changed_line:
                ll = self._main_window.expandtabs(l[0])
                l[0] = ll[0]
                tabs = ll[1:]
                if len(tabs) > 0:
                    i = 0
                    total_shift = 0
                    for j, dl in enumerate(l[3:]):
                        idx = dl[0]
                        cnt = dl[1]
                        while (i < len(tabs)) and (tabs[i][0] < dl[0]):
                            total_shift += tabs[i][2] - 1
                            i += 1
                        idx += total_shift
                        while (i < len(tabs)) and (tabs[i][0] < dl[0] + dl[1]):
                            total_shift += tabs[i][2] - 1
                            cnt += tabs[i][2] - 1
                            i += 1
                        l[j + 3] = (idx, cnt, dl[2])
            else:
                l[0] = l[0].expandtabs(4)

        self.__total_height = max(len(self._old_file), len(self._new_file))
        self.__total_width = max([len(l[0]) for l in self._old_file] + [len(l[0]) for l in self._new_file] + [0]) - 1
        self._left.x_shift = 0
        self._left.y_shift = 0
        self._right.x_shift = 0
        self._right.y_shift = 0
        self._middle.x_shift = 0
        self._middle.y_shift = 0
        self._window.refresh()

    def handle_keypress(self, ch, uni):
        """
        Function handles keypresses in field

        Keyword argument
            ch - code of pressed key or unicode symbol
            uni - True if ch is Unicode char, False otherwise
        """

        if ch == curses.KEY_F12 or ch == 27:
            self._main_window.show_diff_panel()
            return
        elif ch == curses.KEY_UP:
            if self._left.y_shift > 0:
                self._left.y_shift -= 1
                self._right.y_shift -= 1
                self._middle.y_shift -= 1
        elif ch == curses.KEY_LEFT:
            if self._left.x_shift > 0:
                self._left.x_shift -= 1
                self._right.x_shift -= 1
        elif ch == curses.KEY_DOWN:
            if self._left.y_shift + self._left.height < self.__total_height:
                self._left.y_shift += 1
                self._right.y_shift += 1
                self._middle.y_shift += 1
        elif ch == curses.KEY_RIGHT:
            if self._left.x_shift + self._right.width < self.__total_width:
                self._left.x_shift += 1
                self._right.x_shift += 1
        elif ch == curses.KEY_HOME:
            self._left.y_shift = 0
            self._right.y_shift = 0
            self._middle.y_shift = 0
        elif ch == curses.KEY_END:
            if self.__total_height < self._left.height:
                self._left.y_shift = 0
                self._right.y_shift = 0
                self._middle.y_shift = 0
            else:
                self._left.y_shift = self.__total_height - self._left.height
                self._right.y_shift = self.__total_height - self._right.height
                self._middle.y_shift = self.__total_height - self._middle.height
        elif ch == curses.KEY_PPAGE:
            new_pos = self._left.y_shift - self._left.height
            if new_pos >= 0:
                self._left.y_shift = new_pos
                self._right.y_shift = new_pos
                self._middle.y_shift = new_pos
            else:
                self._left.y_shift = 0
                self._right.y_shift = 0
                self._middle.y_shift = 0
        elif ch == curses.KEY_NPAGE:
            if self.__total_height < self._left.height:
                self._left.y_shift = 0
                self._right.y_shift = 0
                self._middle.y_shift = 0
            else:
                new_pos = self._left.y_shift + self._left.height
                if new_pos + self._left.height < self.__total_height:
                    self._left.y_shift = new_pos
                    self._right.y_shift = new_pos
                    self._middle.y_shift = new_pos
                else:
                    self._left.y_shift = self.__total_height - self._left.height
                    self._right.y_shift = self.__total_height - self._right.height
                    self._middle.y_shift = self.__total_height - self._middle.height
        elif ch == int(self._main_window.settings.get(u'Keycodes', u'KEY_CONTROL_UP', False) or -1):
            for i in range(self._middle.y_shift - 1, -1, -1):
                if self._old_file[i][1] != curses.A_NORMAL or self._new_file[i][1] != curses.A_NORMAL:
                    self._left.y_shift = i
                    self._right.y_shift = i
                    self._middle.y_shift = i
                    break
        elif ch == int(self._main_window.settings.get(u'Keycodes', u'KEY_CONTROL_DOWN', False) or -1):
            if self.__total_height < self._left.height:
                self._left.y_shift = 0
                self._right.y_shift = 0
                self._middle.y_shift = 0
            else:
                for i in range(self._middle.y_shift + 1, self.__total_height):
                    if self._old_file[i][1] != curses.A_NORMAL or self._new_file[i][1] != curses.A_NORMAL:
                        if i + self._left.height < self.__total_height:
                            self._left.y_shift = i
                            self._right.y_shift = i
                            self._middle.y_shift = i
                        else:
                            self._left.y_shift = self.__total_height - self._left.height
                            self._right.y_shift = self.__total_height - self._right.height
                            self._middle.y_shift = self.__total_height - self._middle.height
                        break
        elif ch == int(self._main_window.settings.get(u'Keycodes', u'KEY_SHIFT_LEFT', False) or -1):
            self._left.x_shift -= 16
            self._right.x_shift -= 16
            if self._left.x_shift < 0:
                self._left.x_shift = 0
                self._right.x_shift = 0
        elif ch == int(self._main_window.settings.get(u'Keycodes', u'KEY_SHIFT_RIGHT', False) or -1):
           self._left.x_shift += 16
           self._right.x_shift += 16
           if self._left.x_shift + self._right.width > self.__total_width:
               new_shift = self.__total_width - self._right.width
               if new_shift < 0:
                   new_shift = 0
               self._left.x_shift = new_shift
               self._right.x_shift = new_shift
        else:
            return
        self._left.draw()
        self._right.draw()
        self._middle.draw()
        self._window.refresh()

